﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml;
using Shop.Pay.Models;

namespace Shop.Pay.Controller
{
    public class PayController : System.Web.Mvc.Controller
    {
        Models.Pay pinfo = new Models.Pay();
        Models.PostPay ppinfo = new Models.PostPay();
        public String srcMsg = null;
        public String signMsg = null;
        public String verifySrc = null;
        public bool verifyResult = false;



        // GET: Pay
        public ActionResult Index()
        {
            pinfo.merchantId = Request.Form["merchantId"];
            pinfo.version = Request.Form["version"];
            pinfo.language = Request.Form["language"];
            pinfo.signType = Request.Form["signType"];
            pinfo.payType = Request.Form["payType"];
            pinfo.issuerId = Request.Form["issuerId"];
            pinfo.paymentOrderId = Request.Form["paymentOrderId"];
            pinfo.orderNo = Request.Form["orderNo"];
            pinfo.orderDatetime = Request.Form["orderDatetime"];
            pinfo.orderAmount = Request.Form["orderAmount"];
            pinfo.payDatetime = Request.Form["payDatetime"];
            pinfo.payAmount = Request.Form["payAmount"];
            pinfo.ext1 = Request.Form["ext1"];
            pinfo.ext2 = Request.Form["ext2"];
            pinfo.payResult = Request.Form["payResult"];
            pinfo.errorCode = Request.Form["errorCode"];
            pinfo.returnDatetime = Request.Form["returnDatetime"];
            pinfo.key = Request.Form["key"];
            pinfo.signMsg = Request.Form["signMsg"];

            StringBuilder bufSignSrc = new StringBuilder();

            appendSignPara(bufSignSrc, "merchantId", pinfo.merchantId);
            appendSignPara(bufSignSrc, "version", pinfo.version);
            appendSignPara(bufSignSrc, "language", pinfo.language);
            appendSignPara(bufSignSrc, "signType", pinfo.signType);
            appendSignPara(bufSignSrc, "payType", pinfo.payType);
            appendSignPara(bufSignSrc, "issuerId", pinfo.issuerId);
            appendSignPara(bufSignSrc, "paymentOrderId", pinfo.paymentOrderId);
            appendSignPara(bufSignSrc, "orderNo", pinfo.orderNo);
            appendSignPara(bufSignSrc, "orderDatetime", pinfo.orderDatetime);
            appendSignPara(bufSignSrc, "orderAmount", pinfo.orderAmount);
            appendSignPara(bufSignSrc, "payDatetime", pinfo.payDatetime);
            appendSignPara(bufSignSrc, "payAmount", pinfo.payAmount);
            appendSignPara(bufSignSrc, "ext1", pinfo.ext1);
            appendSignPara(bufSignSrc, "ext2", pinfo.ext2);
            appendSignPara(bufSignSrc, "payResult", pinfo.payResult);
            appendSignPara(bufSignSrc, "errorCode", pinfo.errorCode);
            appendLastSignPara(bufSignSrc, "returnDatetime", pinfo.returnDatetime);

            String srcMsg = bufSignSrc.ToString();
            string certpath = Server.MapPath(@"../cert/TLCert.cer");
            bool verifyResult = verify(srcMsg, pinfo.signMsg, certpath, true);//证书路径请参考verify方法的注释设置

            this.verifySrc = srcMsg;
            ViewBag.verifySrc = this.verifySrc;
            this.verifyResult = verifyResult;
            ViewBag.verifyResult = this.verifyResult;
            return View(pinfo);
        }
        public ActionResult tijiao()
        {
            return View();
        }
        public ActionResult PostPay()
        {
            ppinfo.serverUrl = Request.Form["serverUrl"];

            ppinfo.key = Request.Form["key"];
            ppinfo.version = Request.Form["version"];
            ppinfo.language = Request.Form["language"];
            ppinfo.inputCharset = Request.Form["inputCharset"];
            ppinfo.merchantId = Request.Form["merchantId"];
            ppinfo.pickupUrl = Request.Form["pickupUrl"];
            ppinfo.receiveUrl = Request.Form["receiveUrl"];
            ppinfo.payType = Request.Form["payType"];
            ppinfo.signType = Request.Form["signType"];
            ppinfo.orderNo = Request.Form["orderNo"];
            ppinfo.orderAmount = Request.Form["orderAmount"];
            ppinfo.orderDatetime = Request.Form["orderDatetime"];
            ppinfo.orderCurrency = Request.Form["orderCurrency"];
            ppinfo.orderExpireDatetime = Request.Form["orderExpireDatetime"];
            ppinfo.payerTelephone = Request.Form["payerTelephone"];
            ppinfo.payerEmail = Request.Form["payerEmail"];
            ppinfo.payerName = Request.Form["payerName"];
            ppinfo.payerIDCard = Request.Form["payerIDCard"];
            ppinfo.pid = Request.Form["pid"];
            ppinfo.productName = Request.Form["productName"];
            ppinfo.productId = Request.Form["productId"];
            ppinfo.productNum = Request.Form["productNum"];
            ppinfo.productPrice = Request.Form["productPrice"];
            ppinfo.productDesc = Request.Form["productDesc"];
            ppinfo.ext1 = Request.Form["ext1"];
            ppinfo.ext2 = Request.Form["ext2"];
            ppinfo.extTL = Request.Form["extTL"];
            ppinfo.issuerId = Request.Form["issuerId"];
            ppinfo.pan = Request.Form["pan"];
            ppinfo.tradeNature = Request.Form["tradeNature"];

            StringBuilder bufSignSrc = new StringBuilder();
            appendSignPara(bufSignSrc, "inputCharset", ppinfo.inputCharset);
            appendSignPara(bufSignSrc, "pickupUrl", ppinfo.pickupUrl);
            appendSignPara(bufSignSrc, "receiveUrl", ppinfo.receiveUrl);
            appendSignPara(bufSignSrc, "version", ppinfo.version);
            appendSignPara(bufSignSrc, "language", ppinfo.language);
            appendSignPara(bufSignSrc, "signType", ppinfo.signType);
            appendSignPara(bufSignSrc, "merchantId", ppinfo.merchantId);
            appendSignPara(bufSignSrc, "payerName", ppinfo.payerName);
            appendSignPara(bufSignSrc, "payerEmail", ppinfo.payerEmail);
            appendSignPara(bufSignSrc, "payerTelephone", ppinfo.payerTelephone);
            appendSignPara(bufSignSrc, "payerIDCard", ppinfo.payerIDCard);
            appendSignPara(bufSignSrc, "pid", ppinfo.pid);
            appendSignPara(bufSignSrc, "orderNo", ppinfo.orderNo);
            appendSignPara(bufSignSrc, "orderAmount", ppinfo.orderAmount);
            appendSignPara(bufSignSrc, "orderCurrency", ppinfo.orderCurrency);
            appendSignPara(bufSignSrc, "orderDatetime", ppinfo.orderDatetime);
            appendSignPara(bufSignSrc, "orderExpireDatetime", ppinfo.orderExpireDatetime);
            appendSignPara(bufSignSrc, "productName", ppinfo.productName);
            appendSignPara(bufSignSrc, "productPrice", ppinfo.productPrice);
            appendSignPara(bufSignSrc, "productNum", ppinfo.productNum);
            appendSignPara(bufSignSrc, "productId", ppinfo.productId);
            appendSignPara(bufSignSrc, "productDesc", ppinfo.productDesc);
            appendSignPara(bufSignSrc, "ext1", ppinfo.ext1);
            appendSignPara(bufSignSrc, "ext2", ppinfo.ext2);
            appendSignPara(bufSignSrc, "extTL", ppinfo.extTL);
            appendSignPara(bufSignSrc, "payType", ppinfo.payType);
            appendSignPara(bufSignSrc, "issuerId", ppinfo.issuerId);
            appendSignPara(bufSignSrc, "pan", ppinfo.pan);
            appendSignPara(bufSignSrc, "tradeNature", ppinfo.tradeNature);
            appendLastSignPara(bufSignSrc, "key", ppinfo.key);

            srcMsg = bufSignSrc.ToString();
            signMsg = FormsAuthentication.HashPasswordForStoringInConfigFile(srcMsg, "MD5");
            ViewBag.srcMsg = srcMsg;
            ViewBag.signMsg = signMsg;
            return View(ppinfo);
        }
        //---------------------------------------以下代码请勿更动------------------------------------------------------------
        #region 回掉

        public bool SignatureDeformatter(string srcMsgData, string signMsgData, String certPath, Boolean isAbsolatePath)
        {
            //base64解码签名串
            Byte[] signMsgBytes = decode(pinfo.signMsg);
            //源串UTF8格式化
            byte[] srcMsgBytes = System.Text.Encoding.UTF8.GetBytes(srcMsgData);

            //读取x509证书
            X509Certificate2 x509 = new X509Certificate2();
            if (isAbsolatePath)
            {
                //设置证书的绝对路径
                //x509.Import(@"c:\Projects\MyWebSite\cert\TLCert.cer");
                x509.Import(certPath);
            }
            else
            {
                //或者设置证书的相对路径
                //x509.Import(HttpContext.Current.Server.MapPath("../cert/TLCert.cer"));
                x509.Import(Server.MapPath(certPath));
            }

            System.Security.Cryptography.RSACryptoServiceProvider RSA = new System.Security.Cryptography.RSACryptoServiceProvider();

            RSA.FromXmlString(x509.PublicKey.Key.ToXmlString(true));
            System.Security.Cryptography.RSAPKCS1SignatureDeformatter RSADeformatter = new System.Security.Cryptography.RSAPKCS1SignatureDeformatter(RSA);
            //指定解密摘要算法为SHA1
            RSADeformatter.SetHashAlgorithm("SHA1");

            return RSADeformatter.VerifySignature(srcMsgBytes, signMsgBytes);

        }

        /**
         * 根据传入的参数做验签 
         * @param srcMsg 签名用源串
         * @param signMsg 通联响应中给出的签名串
         * @param certPath 证书路径 
         * @param isAbsolatePath 是否绝对路径，如certpath参数值为证书绝对路径，则填true，否则填false
         */
        private bool verify(String srcMsg, String signMsg, String certPath, Boolean isAbsolatePath)
        {
            //base64解码签名串
            Byte[] signMsgBytes = decode(signMsg);

            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            //读取x509证书
            X509Certificate2 x509 = new X509Certificate2();
            if (isAbsolatePath)
            {
                //设置证书的绝对路径
                //x509.Import(@"c:\Projects\MyWebSite\cert\TLCert.cer");
                x509.Import(certPath);
            }
            else
            {
                //或者设置证书的相对路径
                //x509.Import(HttpContext.Current.Server.MapPath("../cert/TLCert.cer"));
                x509.Import(Server.MapPath(certPath));
            }

            //x509.PublicKey.Key.ToXmlString();
            //灌注到rsa
            rsa.FromXmlString(x509.PublicKey.Key.ToXmlString(false));
            bool verifyResult = rsa.VerifyData(System.Text.Encoding.UTF8.GetBytes(srcMsg), "SHA1", signMsgBytes);

            return verifyResult;
        }

        private bool isEmpty(String src)
        {
            if (null == src || "".Equals(src) || "-1".Equals(src))
            {
                return true;
            }
            return false;
        }

        private void appendSignPara(System.Text.StringBuilder buf, String key, String value)
        {
            if (!isEmpty(value))
            {
                buf.Append(key).Append('=').Append(value).Append('&');
            }
        }

        private void appendLastSignPara(System.Text.StringBuilder buf, String key,
                String value)
        {
            if (!isEmpty(value))
            {
                buf.Append(key).Append('=').Append(value);
            }
        }

        //---------------------------------------BASE64------------------------------------------------------------

        /// <summary> Traverse the String until hitting the next Base64 character.
        /// Assumes that there is still another valid Base64 character
        /// left in the String.
        /// </summary>
        private char NextUsefulChar
        {
            get
            {
                char result = '_'; // Start with a non-Base64 character
                while (!isUsefulChar(result))
                {
                    result = mString[mIndex++];
                }

                return result;
            }

        }
        /// <summary> Byte value that maps to 'a' in Base64 encoding
        /// </summary>
        private const int LOWER_CASE_A_VALUE = 26;

        /// <summary> Byte value that maps to '0' in Base64 encoding
        /// </summary>
        private const int ZERO_VALUE = 52;

        /// <summary> Byte value that maps to '+' in Base64 encoding
        /// </summary>
        private const int PLUS_VALUE = 62;

        /// <summary> Byte value that maps to '/' in Base64 encoding
        /// </summary>
        private const int SLASH_VALUE = 63;

        /// <summary> Bit mask for one character worth of bits in Base64 encoding.
        /// Equivalent to binary value 111111b.
        /// </summary>
        private const int SIX_BIT_MASK = 63;

        /// <summary> Bit mask for one byte worth of bits in Base64 encoding.
        /// Equivalent to binary value 11111111b.
        /// </summary>
        private const int EIGHT_BIT_MASK = 0xFF;

        /// <summary> The input String to be decoded
        /// </summary>
        private System.String mString;

        /// <summary> Current position in the String(to be decoded)
        /// </summary>
        private int mIndex = 0;

        /// <summary> Encode an array of bytes using Base64
        /// </summary>
        /// <param name="data[]">The bytes to be encoded
        /// </param>
        /// <returns> A valid Base64 representation of the input
        /// 
        /// </returns>
        public System.String encode(byte[] data)
        {
            // Base64 encoding yields a String that is 33% longer than the byte array
            int charCount = ((data.Length * 4) / 3) + 4;

            // New lines will also be needed for every 76 charactesr, so allocate a
            // StringBuffer that is long enough to hold the full result without
            // having to expand later
            System.Text.StringBuilder result = new System.Text.StringBuilder((charCount * 77) / 76);

            int byteArrayLength = data.Length;
            int byteArrayIndex = 0;
            int byteTriplet = 0;
            while (byteArrayIndex < byteArrayLength - 2)
            {
                // Build the 24 bit byte triplet from the input data
                byteTriplet = convertUnsignedByteToInt(data[byteArrayIndex++]);
                // Each input byte contributes 8 bits to the triplet
                byteTriplet <<= 8;
                byteTriplet |= convertUnsignedByteToInt(data[byteArrayIndex++]);
                byteTriplet <<= 8;
                byteTriplet |= convertUnsignedByteToInt(data[byteArrayIndex++]);

                // Look at the lowest order six bits and remember them
                byte b4 = (byte)(SIX_BIT_MASK & byteTriplet);
                // Move the byte triplet to get the next 6 bit value
                byteTriplet >>= 6;
                byte b3 = (byte)(SIX_BIT_MASK & byteTriplet);
                byteTriplet >>= 6;
                byte b2 = (byte)(SIX_BIT_MASK & byteTriplet);
                byteTriplet >>= 6;
                byte b1 = (byte)(SIX_BIT_MASK & byteTriplet);

                // Add the Base64 encoded character to the result String
                result.Append(mapByteToChar(b1));
                result.Append(mapByteToChar(b2));
                result.Append(mapByteToChar(b3));
                result.Append(mapByteToChar(b4));

                // There are 57 bytes for every 76 characters, so wrap the line when needed
                //if ( byteArrayIndex % 57 == 0 ) {
                //    result.append( "\n" );
                //}
            }

            // Check if we have one byte left over
            if (byteArrayIndex == byteArrayLength - 1)
            {
                // Convert our one byte to an int
                byteTriplet = convertUnsignedByteToInt(data[byteArrayIndex++]);
                // Right pad the second 6 bit value with zeros
                byteTriplet <<= 4;

                byte b2 = (byte)(SIX_BIT_MASK & byteTriplet);
                byteTriplet >>= 6;
                byte b1 = (byte)(SIX_BIT_MASK & byteTriplet);

                result.Append(mapByteToChar(b1));
                result.Append(mapByteToChar(b2));

                // Add "==" to the output to make it a multiple of 4 Base64 characters
                result.Append("==");
            }

            // Check if we have two byte left over
            if (byteArrayIndex == byteArrayLength - 2)
            {
                // Convert our two bytes to an int
                byteTriplet = convertUnsignedByteToInt(data[byteArrayIndex++]);
                byteTriplet <<= 8;
                byteTriplet |= convertUnsignedByteToInt(data[byteArrayIndex++]);
                // Right pad the third 6 bit value with zeros
                byteTriplet <<= 2;

                byte b3 = (byte)(SIX_BIT_MASK & byteTriplet);
                byteTriplet >>= 6;
                byte b2 = (byte)(SIX_BIT_MASK & byteTriplet);
                byteTriplet >>= 6;
                byte b1 = (byte)(SIX_BIT_MASK & byteTriplet);

                result.Append(mapByteToChar(b1));
                result.Append(mapByteToChar(b2));
                result.Append(mapByteToChar(b3));

                // Add "==" to the output to make it a multiple of 4 Base64 characters
                result.Append("=");
            }

            return result.ToString();
        }


        /// <summary> Decode an input String using Base64
        /// </summary>
        /// <param name="data">The String to be decoded
        /// </param>
        /// <returns> The appropriate byte array
        /// 
        /// </returns>
        public byte[] decode(System.String data)
        {
            mString = data;
            mIndex = 0;

            /// <summary> Total number of Base64 characters in the input
            /// </summary>
            int mUsefulLength = 0;
            int length = mString.Length;
            for (int i = 0; i < length; i++)
            {
                if (isUsefulChar(mString[i]))
                {
                    mUsefulLength++;
                }
            }

            //mString = data;


            // A Base64 byte array is 75% the size of its String representation
            int byteArrayLength = mUsefulLength * 3 / 4;

            byte[] result = new byte[byteArrayLength];

            int byteTriplet = 0;
            int byteIndex = 0;

            // Continue until we have less than 4 full characters left to
            // decode in the input.
            while (byteIndex + 2 < byteArrayLength)
            {

                // Package a set of four characters into a byte triplet
                // Each character contributes 6 bits of useful information
                byteTriplet = mapCharToInt(NextUsefulChar);
                byteTriplet <<= 6;
                byteTriplet |= mapCharToInt(NextUsefulChar);
                byteTriplet <<= 6;
                byteTriplet |= mapCharToInt(NextUsefulChar);
                byteTriplet <<= 6;
                byteTriplet |= mapCharToInt(NextUsefulChar);

                // Grab a normal byte (eight bits) out of the byte triplet
                // and put it in the byte array
                result[byteIndex + 2] = (byte)(byteTriplet & EIGHT_BIT_MASK);
                byteTriplet >>= 8;
                result[byteIndex + 1] = (byte)(byteTriplet & EIGHT_BIT_MASK);
                byteTriplet >>= 8;
                result[byteIndex] = (byte)(byteTriplet & EIGHT_BIT_MASK);
                byteIndex += 3;
            }

            // Check if we have one byte left to decode
            if (byteIndex == byteArrayLength - 1)
            {
                // Take out the last two characters from the String
                byteTriplet = mapCharToInt(NextUsefulChar);
                byteTriplet <<= 6;
                byteTriplet |= mapCharToInt(NextUsefulChar);

                // Remove the padded zeros
                byteTriplet >>= 4;
                result[byteIndex] = (byte)(byteTriplet & EIGHT_BIT_MASK);
            }

            // Check if we have two bytes left to decode
            if (byteIndex == byteArrayLength - 2)
            {
                // Take out the last three characters from the String
                byteTriplet = mapCharToInt(NextUsefulChar);
                byteTriplet <<= 6;
                byteTriplet |= mapCharToInt(NextUsefulChar);
                byteTriplet <<= 6;
                byteTriplet |= mapCharToInt(NextUsefulChar);

                // Remove the padded zeros
                byteTriplet >>= 2;
                result[byteIndex + 1] = (byte)(byteTriplet & EIGHT_BIT_MASK);
                byteTriplet >>= 8;
                result[byteIndex] = (byte)(byteTriplet & EIGHT_BIT_MASK);
            }

            return result;
        }

        /// <summary> Convert a Base64 character to its 6 bit value as defined by the mapping.
        /// </summary>
        /// <param name="c">Base64 character to decode
        /// </param>
        /// <returns> int representation of 6 bit value
        /// 
        /// </returns>
        private int mapCharToInt(char c)
        {
            if (c >= 'A' && c <= 'Z')
            {
                return c - 'A';
            }

            if (c >= 'a' && c <= 'z')
            {
                return (c - 'a') + LOWER_CASE_A_VALUE;
            }

            if (c >= '0' && c <= '9')
            {
                return (c - '0') + ZERO_VALUE;
            }

            if (c == '+')
            {
                return PLUS_VALUE;
            }

            if (c == '/')
            {
                return SLASH_VALUE;
            }

            throw new System.ArgumentException(c + " is not a valid Base64 character.");
        }


        /// <summary> Convert a byte between 0 and 63 to its Base64 character equivalent
        /// </summary>
        /// <param name="b">Byte value to be converted
        /// </param>
        /// <returns> Base64 char value
        /// 
        /// </returns>
        private char mapByteToChar(byte b)
        {
            if (b < LOWER_CASE_A_VALUE)
            {
                return (char)('A' + b);
            }

            if (b < ZERO_VALUE)
            {
                return (char)('a' + (b - LOWER_CASE_A_VALUE));
            }

            if (b < PLUS_VALUE)
            {
                return (char)('0' + (b - ZERO_VALUE));
            }

            if (b == PLUS_VALUE)
            {
                return '+';
            }

            if (b == SLASH_VALUE)
            {
                return '/';
            }

            throw new System.ArgumentException("Byte " + b + " is not a valid Base64 value");
        }

        /// <param name="c">Character to be examined
        /// </param>
        /// <returns> Whether or not the character is a Base64 character
        /// 
        /// </returns>
        private bool isUsefulChar(char c)
        {
            return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '+') || (c == '/');
        }


        /// <summary> Convert a byte to an integer.  Needed because in Java bytes
        /// are signed, and for Base64 purposes they are not.  If not done
        /// this way, when converted to an int, 0xFF will become -127
        /// </summary>
        /// <param name="b">Byte value to be converted
        /// </param>
        /// <returns> Value as an integer, as if byte was unsigned
        /// 
        /// </returns>
        private int convertUnsignedByteToInt(byte b)
        {
            if (b >= 0)
            {
                return (int)b;
            }

            return 256 + b;
        }
        #endregion

        #region 提交测试
        //---------------------------------------以下代码请勿更动------------------------------------------------------------

        //private bool isEmpty(String src)
        //{
        //    if (null == src || "".Equals(src) || "-1".Equals(src))
        //    {
        //        return true;
        //    }
        //    return false;
        //}

        //private void appendSignPara(System.Text.StringBuilder buf, String key, String value)
        //{
        //    if (!isEmpty(value))
        //    {
        //        buf.Append(key).Append('=').Append(value).Append('&');
        //    }
        //}

        //private void appendLastSignPara(System.Text.StringBuilder buf, String key,
        //        String value)
        //{
        //    if (!isEmpty(value))
        //    {
        //        buf.Append(key).Append('=').Append(value);
        //    }
        //}
        #endregion
    }
}